`timescale 1ns / 1ps

// Latency = 3 + N_PE
// N_PE >= 2
module pwc_dspchain
#(
    parameter N_PE      = 4,
    parameter BIT_I     = 8,
    parameter BIT_W     = 5,
    parameter BIT_O     = 16,
    parameter LSHIFT    = 20
)
(
    input   clk,
    input   ce,
    input   ce_acc,
    input   ld_acc,
    
    input   [N_PE*1*BIT_I-1 : 0]     i_data,
    input   [N_PE*2*BIT_W-1 : 0]     i_weight,
    
    output  [2*BIT_O-1 : 0]          o_data
);

wire [1*BIT_I-1 : 0] si_da [N_PE-1 : 0];
wire [2*BIT_W-1 : 0] si_wt [N_PE-1 : 0];
wire [1*BIT_I-1 : 0] so_da [N_PE-1 : 0];
wire [2*BIT_W-1 : 0] so_wt [N_PE-1 : 0];

wire [26 : 0] ain [N_PE-1 : 0];
wire [26 : 0] din [N_PE-1 : 0];
wire [17 : 0] bin [N_PE-1 : 0];
wire [47 : 0] pchain [N_PE-2 : 0];
wire [47 : 0] acc_out;

wire [  BIT_O-1 : 0] o_lo;
wire [  BIT_O-1 : 0] o_hi;

assign o_data = { o_hi, o_lo };
assign o_lo = acc_out[       BIT_O-1 : 0];
assign o_hi = acc_out[LSHIFT+BIT_O-1 : LSHIFT] + acc_out[LSHIFT-1]; // 16b + 1b

dsp_dotprod_tail chain_tailer
(
    .CLK      (clk),              // input wire CLK
    .SEL      (ld_acc),           // input wire [0 : 0] SEL
    .PCIN     (pchain[N_PE-2]),   // input wire [47 : 0] PCIN
    // .PCIN     (48'b0),
    .A        (ain[N_PE-1]),      // input wire [26 : 0] A
    .B        (bin[N_PE-1]),      // input wire [17 : 0] B
    .D        (din[N_PE-1]),      // input wire [26 : 0] D
    .P        (acc_out),          // output wire [47 : 0] P
    .CED3     (ce),               // input wire CED3
    .CEA3     (ce),               // input wire CEA3
    .CEA4     (ce),               // input wire CEA4
    .CEB3     (ce),               // input wire CEB3
    .CEB4     (ce),               // input wire CEB4
    .CEM      (ce),               // input wire CEM
    .CEP      (ce_acc),           // input wire CEP
    .CESEL5   (ce)                // input wire CESEL5
);

dsp_dotprod_head chain_header
(
    .CLK        (clk),              // input wire CLK
    .CE         (ce),               // input wire CE
    .A          (ain[0]),           // input wire [26 : 0] A
    .B          (bin[0]),           // input wire [17 : 0] B
    .D          (din[0]),           // input wire [26 : 0] D
    .PCOUT      (pchain[0]),        // output wire [47 : 0] PCOUT
    .P()                            // output wire [47 : 0] P
);

genvar i;
generate
    // delay line & sign extend logic
    for (i = 0; i < N_PE; i = i + 1)
    begin
        assign si_da[i] =   i_data[  BIT_I * i +   BIT_I - 1 :   BIT_I * i];
        assign si_wt[i] = i_weight[2*BIT_W * i + 2*BIT_W - 1 : 2*BIT_W * i];
        shift_reg
        #(
            .N          (i),
            .WIDTH      (1 * BIT_I + 2 * BIT_W)
        ) delay_line
        (
            .clk        (clk),
            .clken      (ce),
            .i_data     ({ si_da[i], si_wt[i] }),
            .o_data     ({ so_da[i], so_wt[i] })
        );
        // sign extend 5 --> 27
        assign ain[i] = { {22{so_wt[i][4]}}, so_wt[i][4 : 0] };
        // sign extend 5 --> 27 & left shift 20
        assign din[i] = { { 2{so_wt[i][9]}}, so_wt[i][9 : 5], 20'b0 };
        // unsigned extend 8 --> 18
        assign bin[i] = { 10'b0, so_da[i] };
    end

    // dsp chain
    for (i = 1; i < N_PE - 1; i = i + 1)
    begin
        dsp_dotprod_body chain_body
        (
            .CLK      (clk),          // input wire CLK
            .CE       (ce),           // input wire CE
            .PCIN     (pchain[i-1]),    // input wire [47 : 0] PCIN
            // .PCIN     (48'b0),
            .A        (ain[i]),       // input wire [26 : 0] A
            .B        (bin[i]),       // input wire [17 : 0] B
            .D        (din[i]),       // input wire [26 : 0] D
            .PCOUT    (pchain[i]),    // output wire [47 : 0] PCOUT
            .P        ()              // output wire [47 : 0] P
        );
    end
endgenerate

endmodule


// Without delay line
// Latency = 3 + N_PE
// N_PE >= 2
module pwc2_dspchain
#(
    parameter N_PE      = 4,
    parameter BIT_I     = 8,
    parameter BIT_W     = 5,
    parameter BIT_O     = 16,
    parameter LSHIFT    = 20
)
(
    input   clk,
    input   ce,
    input   ce_acc,
    input   ld_acc,
    
    input   [N_PE*1*BIT_I-1 : 0]     i_data,
    input   [N_PE*2*BIT_W-1 : 0]     i_weight,
    
    output  [2*BIT_O-1 : 0]          o_data
);

wire [1*BIT_I-1 : 0] i_da [N_PE-1 : 0];
wire [2*BIT_W-1 : 0] i_wt [N_PE-1 : 0];

wire [26 : 0] ain [N_PE-1 : 0];
wire [26 : 0] din [N_PE-1 : 0];
wire [17 : 0] bin [N_PE-1 : 0];
wire [47 : 0] pchain [N_PE-2 : 0];
wire [47 : 0] acc_out;

wire [  BIT_O-1 : 0] o_lo;
wire [  BIT_O-1 : 0] o_hi;

assign o_data = { o_hi, o_lo };
assign o_lo = acc_out[       BIT_O-1 : 0];
assign o_hi = acc_out[LSHIFT+BIT_O-1 : LSHIFT] + acc_out[LSHIFT-1]; // 16b + 1b

dsp_dotprod_tail chain_tailer
(
    .CLK      (clk),              // input wire CLK
    .SEL      (ld_acc),           // input wire [0 : 0] SEL
    .PCIN     (pchain[N_PE-2]),   // input wire [47 : 0] PCIN
    // .PCIN     (48'b0),
    .A        (ain[N_PE-1]),      // input wire [26 : 0] A
    .B        (bin[N_PE-1]),      // input wire [17 : 0] B
    .D        (din[N_PE-1]),      // input wire [26 : 0] D
    .P        (acc_out),          // output wire [47 : 0] P
    .CED3     (ce),               // input wire CED3
    .CEA3     (ce),               // input wire CEA3
    .CEA4     (ce),               // input wire CEA4
    .CEB3     (ce),               // input wire CEB3
    .CEB4     (ce),               // input wire CEB4
    .CEM      (ce),               // input wire CEM
    .CEP      (ce_acc),           // input wire CEP
    .CESEL5   (ce)                // input wire CESEL5
);

dsp_dotprod_head chain_header
(
    .CLK        (clk),              // input wire CLK
    .CE         (ce),               // input wire CE
    .A          (ain[0]),           // input wire [26 : 0] A
    .B          (bin[0]),           // input wire [17 : 0] B
    .D          (din[0]),           // input wire [26 : 0] D
    .PCOUT      (pchain[0]),        // output wire [47 : 0] PCOUT
    .P()                            // output wire [47 : 0] P
);

genvar i;
generate
    // delay line & sign extend logic
    for (i = 0; i < N_PE; i = i + 1)
    begin
        assign i_da[i] =   i_data[  BIT_I * i +   BIT_I - 1 :   BIT_I * i];
        assign i_wt[i] = i_weight[2*BIT_W * i + 2*BIT_W - 1 : 2*BIT_W * i];
        // sign extend 5 --> 27
        assign ain[i] = { {22{i_wt[i][4]}}, i_wt[i][4 : 0] };
        // sign extend 5 --> 27 & left shift 20
        assign din[i] = { { 2{i_wt[i][9]}}, i_wt[i][9 : 5], 20'b0 };
        // unsigned extend 8 --> 18
        assign bin[i] = { 10'b0, i_da[i] };
    end

    // dsp chain
    for (i = 1; i < N_PE - 1; i = i + 1)
    begin
        dsp_dotprod_body chain_body
        (
            .CLK      (clk),          // input wire CLK
            .CE       (ce),           // input wire CE
            .PCIN     (pchain[i-1]),    // input wire [47 : 0] PCIN
            // .PCIN     (48'b0),
            .A        (ain[i]),       // input wire [26 : 0] A
            .B        (bin[i]),       // input wire [17 : 0] B
            .D        (din[i]),       // input wire [26 : 0] D
            .PCOUT    (pchain[i]),    // output wire [47 : 0] PCOUT
            .P        ()              // output wire [47 : 0] P
        );
    end
endgenerate

endmodule

